/*
 * Project: svg-generate
 * File Created: Sunday, 2023-08-13 13:46:41
 * Author: jianghong (jianghong2020@qq.com)
 * -----
 * Last Modified: Sunday, 2023-08-13 13:46:41
 * Modified By: jianghong (jianghong2020@qq.com)
 */

import { ColorObject, SvgColor } from "./Color";
import { SelectObject, StageObecjArray, UseObjectValue, getArgsFromPathD, panelTitle, } from "./ObjectUtils";
import { StageObject, TransformObject, FilterMultipleValueObject } from "./StageObject";
export enum FillRule {
    NONZERO = 'nonzero',
    EVENODD = 'evenodd'
}
export enum ElementObjectType {
    none,
    rect,
    path,
    ellipse,
    text,
    svg,
    use,
    group,
    link,
    linearGradient,
    radialGradient,
    filter,
    polygon,
    defs,
    circle,
    clipPath,
}
export enum PathDrawMethod {
    M = 'M',
    L = 'L',
    Z = 'Z',
    Q = 'Q',
    C = 'C',
    S = 'S',
    A = 'A',
    H = 'H',
    V = 'V',
    T = 'T',
    m = 'm',
    l = 'l',
    z = 'z',
    q = 'q',
    s = 's',
    c = 'c',
    a = 'a',
    h = 'h',
    v = 'v',
    t = 't',
}
export interface PathDrawItem {
    method: PathDrawMethod,
    point: PathPoint[] | string
}

/**
 * 元素组件
 */
export class ElementObject extends StageObject {
    @panelTitle('名称')
    public name: string = '对象0';
    public initX = 0;
    public initY = 0;
    @panelTitle('左边')
    public x: number = 0;
    @panelTitle('顶部')
    public y: number = 0;
    @panelTitle('画笔颜色')
    public stroke: ColorObject = new SvgColor();
    @panelTitle('画笔大小')
    public strokeWidth: number = 1;
    @panelTitle('虚线分段')
    public strokeDasharray = '';
    @panelTitle('线段末端')
    public strokeLinecap: SelectObject = new SelectObject('', [
        {
            title: '默认',
            value: 'inherit'
        },
        {
            title: '圆角',
            value: 'round'
        }, {
            title: '方形',
            value: 'square'
        }, {
            title: '平头',
            value: 'butt'
        }
    ]);
    @panelTitle('线段连接')
    public strokeLinejoin: SelectObject = new SelectObject('', [
        {
            title: '圆弧',
            value: 'arcs'
        },
        {
            title: '圆角',
            value: 'round'
        }, {
            title: '斜接',
            value: 'miter'
        }, {
            title: '方形',
            value: 'bevel'
        },
        {
            title: '延长斜接',
            value: 'miter-clip'
        }
    ]);
    @panelTitle('画笔偏移')
    public strokeDashoffset = 0
    @panelTitle('填充')
    public fill: ColorObject = new SvgColor('rgba(0,0,0,0)');
    @panelTitle('填充度')
    public fillOpacity: number = 1;
    public fillRule: FillRule = FillRule.NONZERO;
    public path: PathDrawItem[] = [];
    @panelTitle('滤镜')
    public filters = new FilterMultipleValueObject();
    @panelTitle('变换')
    public transform: TransformObject = new TransformObject();
    @panelTitle('剪切')
    public clipPath: SelectObject = new SelectObject('', []);
    public set new_clipPath(t: string) {
        this.clipPath.value = t;
    }
    constructor() {
        super();
        this.clipPath.type = 'refs';
        this.transform.parent = this;
    }
    get pathString() {
        return this.pathToString();
    }
    pathToString(path: PathDrawItem[] = []) {
        let s = '';
        const ar = path.length > 0 ? path : this.path;
        for (let index = 0; index < ar.length; index++) {
            const item = ar[index];
            if (item.method === PathDrawMethod.Z) {
                s += item.method;
            } else if (typeof item.point === 'string') {
                s += item.method + item.point;
            } else if (item.method === PathDrawMethod.h || item.method === PathDrawMethod.H) {
                s += item.method + item.point[0].x;
            } else if (item.method === PathDrawMethod.v || item.method === PathDrawMethod.V) {
                s += item.method + item.point[0].y;
            } else {
                let p = '';
                for (let a = 0; a < item.point.length; a++) {
                    const point = item.point[a];
                    if (p) {
                        p += ',' + (point.x + this.x) + ' ' + (point.y + this.y)
                    } else {
                        p += (point.x + this.x) + ' ' + (point.y + this.y)
                    }
                }
                s += item.method + p;
            }

        }

        return s + (this.closed ? ' Z' : '');
    }

    closePath() {
        if (this.type !== ElementObjectType.path) {
            return
        }
        this.path.push({
            method: PathDrawMethod.Z,
            point: [{
                x: 0,
                y: 0
            }]
        })
    }

    setBaseAttr(node: SVGElement) {
        super.setBaseAttr(node)
        this.fill = new SvgColor(node.getAttribute('fill') || 'rgba(0, 0, 0, 0)');
        this.stroke = new SvgColor(node.getAttribute('stroke') || 'rgba(0, 0, 0, 0)');
        this.strokeWidth = Number(node.getAttribute('stroke-width') || 1);
        this.fillOpacity = Number(node.getAttribute('fill-opacity') || 1);
        this.strokeDasharray = node.getAttribute('stroke-dasharray') || '';
        this.strokeDashoffset = Number(node.getAttribute('stroke-dashoffset') || 0);
        this.fillRule = node.getAttribute('fill-rule') as FillRule || FillRule.NONZERO;
        const strokeLinecap = node.getAttribute('stroke-linecap')
        if (strokeLinecap) {
            this.strokeLinecap.value = strokeLinecap;
        }
        const strokeLinejoin = node.getAttribute('stroke-linejoin')
        if (strokeLinejoin) {
            this.strokeLinejoin.value = strokeLinejoin;
        }
    }
}

export class RectObject extends ElementObject {
    @panelTitle('宽度')
    public width = 100;
    @panelTitle('高度')
    public height = 60;
    @panelTitle('圆角X')
    public rx = 0;
    @panelTitle('圆角Y')
    public ry = 0;
    @panelTitle('动画')
    public children = new StageObecjArray<StageObject>;
    constructor(x: number, y: number) {
        super();
        this.x = x;
        this.y = y;
        this.initX = x;
        this.initY = y;
        this.type = ElementObjectType.rect;
        this.name = '矩形';
    }
    static fromNode(node: SVGElement) {
        const x = Number(node.getAttribute('x'));
        const y = Number(node.getAttribute('y'));
        const rect = new RectObject(x, y);
        rect.width = Number(node.getAttribute('width'));
        rect.height = Number(node.getAttribute('height'));
        rect.rx = Number(node.getAttribute('rx'));
        rect.ry = Number(node.getAttribute('ry'));
        rect.setBaseAttr(node);
        return rect;
    }
}
export class EllipseObject extends ElementObject {
    @panelTitle('半径X')
    public rx = 40;
    @panelTitle('半径Y')
    public ry = 30;
    @panelTitle('动画')
    public children = new StageObecjArray<StageObject>;
    constructor(cx: number, cy: number) {
        super();
        this.x = cx;
        this.y = cy;
        this.initX = cx;
        this.initY = cy;
        this.type = ElementObjectType.ellipse;
        this.name = '椭圆';
    }
    static fromNode(node: SVGElement) {
        const cx = Number(node.getAttribute('cx'));
        const cy = Number(node.getAttribute('cy'));
        const rx = Number(node.getAttribute('rx'));
        const ry = Number(node.getAttribute('ry'));
        const ellipse = new EllipseObject(cx, cy);
        ellipse.rx = rx;
        ellipse.ry = ry;
        ellipse.setBaseAttr(node);
        return ellipse;
    }
}
export class CircleObject extends ElementObject {
    @panelTitle('半径')
    public r = 10;
    @panelTitle('动画')
    public children = new StageObecjArray<StageObject>;
    constructor(cx: number, cy: number, r: number) {
        super();
        this.r = r;
        this.x = cx;
        this.y = cy;
        this.initX = cx;
        this.initY = cy;
        this.type = ElementObjectType.circle;
        this.name = '圆';
    }
    static fromNode(node: SVGElement) {
        const cx = Number(node.getAttribute('cx'));
        const cy = Number(node.getAttribute('cy'));
        const r = Number(node.getAttribute('r'));
        const circle = new CircleObject(cx, cy, r);
        circle.setBaseAttr(node);
        return circle;
    }
}
export class TextObject extends ElementObject {
    @panelTitle('文本内容')
    public text = 'hello';
    @panelTitle('字体大小')
    public fontSize = 16;
    @panelTitle('文本字体')
    public fontFamily: string = 'inherit';
    @panelTitle('动画')
    public children = new StageObecjArray<StageObject>;
    public textLength: string = 'None';
    constructor(x: number, y: number) {
        super();
        this.x = x;
        this.y = y;
        this.initX = x;
        this.initY = y;
        this.type = ElementObjectType.text;
        this.name = 'text';
    }
    static fromNode(node: SVGElement) {
        const x = Number(node.getAttribute('x'));
        const y = Number(node.getAttribute('y'));
        const text = new TextObject(x, y);

        const id = node.getAttribute('id');
        if (id) {
            text.id = id;
        }
        text.text = node.textContent || '';
        text.fontSize = Number(node.getAttribute('font-size') || 16);
        text.fontFamily = node.getAttribute('font-family') || 'inherit';

        text.setBaseAttr(node);

        return text
    }
}
export class PathObject extends ElementObject {
    @panelTitle('闭合路径')
    public closed: boolean = false;
    @panelTitle('动画')
    public children = new StageObecjArray<StageObject>;
    public editPoints: boolean = true;
    constructor(startX: number, startY: number) {
        super();
        this.x = startX;
        this.y = startY;
        this.initX = startX;
        this.initY = startY;
        this.type = ElementObjectType.path;
        this.name = 'path';
        this.moveTo(0, 0);
    }
    moveTo(x: number, y: number) {
        this.path.push({
            method: PathDrawMethod.M,
            point: [{
                x,
                y
            }]
        })
    }
    lineTo(x: number, y: number) {
        this.path.push({
            method: PathDrawMethod.L,
            point: [{
                x,
                y
            }]
        })
    }
    closePath() {
        this.path.push({
            method: PathDrawMethod.Z,
            point: [{
                x: 0,
                y: 0
            }]
        })
    }
    /**
     * 两次曲线
     * @param contrX 
     * @param contrY 
     * @param endX 
     * @param endY 
     */
    quadraticCurveTo(contrX: number, contrY: number, endX: number, endY: number) {
        this.path.push({
            method: PathDrawMethod.Q,
            point: [{
                x: contrX,
                y: contrY
            },
            {
                x: endX,
                y: endY
            }]
        })
    }
    /**
     * 三次曲线
     * @param contrX1 
     * @param contrY1 
     * @param contrX2 
     * @param contrY2 
     * @param endX 
     * @param endY 
     */
    cubicCurveTo(contrX1: number, contrY1: number, contrX2: number, contrY2: number, endX: number, endY: number) {
        this.path.push({
            method: PathDrawMethod.C,
            point: [{
                x: contrX1,
                y: contrY1
            },
            {
                x: contrX2,
                y: contrY2
            }, {
                x: endX,
                y: endY
            }]
        })
    }
    /**
     * 解析路径
     * @param path 
     * @returns 
     */
    public parsePath(path: string) {
        const ar: PathDrawItem[] = []
        const reg = /([MLHVCSQTAZmlhvcsqtaz])([^MLHVCSQTAZmlhvcsqtaz]*)/g
        const res = path.matchAll(reg)
        for (const item of res) {
            const method = item[1] as PathDrawMethod;
            let point: PathPoint[] | string = []
            const tmpVals = item[2]
            if (method === 'm' || method === 'M') {
                const vals = getArgsFromPathD(tmpVals)
                point.push({
                    x: parseFloat(vals[0]),
                    y: parseFloat(vals[1])
                })
            } else if (method === 'l' || method === 'L') {
                const vals = getArgsFromPathD(tmpVals)
                for (let i = 0; i < vals.length; i += 2) {
                    point.push({
                        x: parseFloat(vals[i]),
                        y: parseFloat(vals[i + 1])
                    })
                }
            } else if (method === 'h' || method === 'H') {
                const vals = getArgsFromPathD(tmpVals)
                point.push({
                    x: parseFloat(vals[0]),
                    y: this.y
                })
            } else if (method === 'v' || method === 'V') {
                const vals = getArgsFromPathD(tmpVals)
                point.push({
                    x: this.x,
                    y: parseFloat(vals[0])
                })
            } else if (method === 'c' || method === 'C') {
                const vals = getArgsFromPathD(tmpVals)
                point.push({
                    x: parseFloat(vals[0]),
                    y: parseFloat(vals[1])
                })
                point.push({
                    x: parseFloat(vals[2]),
                    y: parseFloat(vals[3])
                })
                point.push({
                    x: parseFloat(vals[4]),
                    y: parseFloat(vals[5])
                })
            } else if (method === 's' || method === 'S') {
                point = tmpVals
            } else if (method === 'q' || method === 'Q') {
                const vals = getArgsFromPathD(tmpVals)
                point.push({
                    x: parseFloat(vals[0]),
                    y: parseFloat(vals[1])
                })
                point.push({
                    x: parseFloat(vals[2]),
                    y: parseFloat(vals[3])
                })
            } else if (method === 't' || method === 'T') {
                point = tmpVals
            } else if (method === 'a' || method === 'A') {
                point = tmpVals
            }

            ar.push({
                method,
                point
            })
        }
        return ar
    }

    static fromNode(node: SVGElement) {
        const path = new PathObject(0, 0);
        path.setBaseAttr(node);
        path.path = path.parsePath(node.getAttribute('d') || '');
        return path;
    }
}
/**
 * 多边形
 */
export class PolygonObject extends PathObject {
    public name: string = '多边形';
    @panelTitle('边数')
    public sideCount: number = 3;
    @panelTitle('边长')
    public side: number = 80;
    public closed: boolean = true;
    public editPoints: boolean = false;
    get pathString() {
        if (this.sideCount < 3) {
            this.sideCount = 3;
        }
        const a = 360 / this.sideCount / 180 * Math.PI;
        const d = 360 / this.sideCount;
        const r = Math.sin(a) * (this.side / 2);
        const points: PathDrawItem[] = [];
        for (let i = 0; i < 360; i += d) {
            const ia = i / 180 * Math.PI;
            const x = Math.cos(ia) * r;
            const y = Math.sin(ia) * r;
            let m = i == 0 ? PathDrawMethod.M : PathDrawMethod.L;
            points.push({
                method: m,
                point: [{ x, y }]
            })
        }
        return super.pathToString(points);
    }
}

/**
 * 组合器
 */
export class GroupObject extends ElementObject {
    @panelTitle('动画')
    public children = new StageObecjArray<StageObject>;
    constructor() {
        super();
        this.name = 'group';
        this.type = ElementObjectType.group;
    }
    static fromNode(node: SVGElement) {
        const group = new GroupObject();
        group.x = Number(node.getAttribute('x') || 0);
        group.y = Number(node.getAttribute('y') || 0);
        group.setBaseAttr(node);

        return group;
    }
}

/**
 * 引用器
 */
export class UseObject extends ElementObject {
    @panelTitle('引用')
    public href: UseObjectValue = new UseObjectValue('');
    @panelTitle('宽度')
    public width = 100;
    @panelTitle('高度')
    public height = 60;
    @panelTitle('动画')
    public children = new StageObecjArray<StageObject>;
    constructor(x: number, y: number, href?: UseObjectValue) {
        super();
        this.x = x;
        this.y = y;
        this.initX = x;
        this.initY = y;
        this.name = 'use';
        this.type = ElementObjectType.use;
        if (href !== undefined) {
            this.href = href;
        }
    }
    static fromNode(node: SVGElement) {
        const use = new UseObject(0, 0);
        use.href = new UseObjectValue((node.getAttribute('href') || '').replace('#', ''));
        use.width = Number(node.getAttribute('width') || 100);
        use.height = Number(node.getAttribute('height') || 60);
        use.x = Number(node.getAttribute('x') || 0);
        use.y = Number(node.getAttribute('y') || 0);
        use.setBaseAttr(node);
        return use;
    }
}

export class FiltersObject extends ElementObject { }

export class LinkObject extends ElementObject {
    public name = '超链接';
    @panelTitle('链接')
    public href: string = '';
    public type: ElementObjectType = ElementObjectType.link;
}

export class ClipPathObject extends ElementObject {
    public name = '剪切';
    public clipPathUnits: ClipPathObjectclipPathUnits = 'userSpaceOnUse';
    public type: ElementObjectType = ElementObjectType.clipPath;
}